(function( $ ){
/**
 * @name script
 */
/**
     * the base object for the storeLocator code.
     * @author Jeremy Swink swinkj@gsicommerce.com
     * @class
     * @static
     * @version 2.0.1
     */
storeLocator = {
    /**
     * @ignore
     */
    setPars: function(){
        return {
            /**
             * Shows the left pane "results list" on initial load
             * @type {boolean}
             * @default false
             * @memberOf storeLocator.pars
             * @name initResultList
             */
            initResultList: false,
            /**
             * Accepted values: 'inline', 'popup', 'false' Sets the type of directions to show
             * @type {string}
             * @default inline
             * @memberOf storeLocator.pars
             * @name directionsType
             */
            directionsType: 'inline',
            /**
             * Accepts an element id for finding the container for inline directions. Set to false if using "popup" directions
             * @type {string}
             * @default directions
             * @memberOf storeLocator.pars
             * @name directionsContainerID
             */
            directionsContainerID: 'directions',
            /**
             * Config object for the tooltips. See the documentation for the jquery.tooltip class. Set to false if no tooltips are desired or you are using your own tooltip class
             * @type {object}
             * @memberOf storeLocator.pars
             * @name defaultToolTips
             */
            defaultToolTips: {
                selector:'.tooltip-controller',
                pars:{
                }
            },
            /**
             * If true the welcome screen will automatically close when a search is initiated
             * @type {boolean}
             * @default true
             * @memberOf storeLocator.pars
             * @name autoCloseWelcome
             */
            autoCloseWelcome: true,
            /**
             * If true the "results pane" will open when an error is returned, displaying the error
             * @type {boolean}
             * @default true
             * @memberOf storeLocator.pars
             * @name openResultPaneErrorBox
             */
            openResultPaneErrorBox: true,
            /**
             * Use Google Street View
             * @type {boolean}
             * @default true
             * @memberOf storeLocator.pars
             * @name enableStreetView
             */
            enableStreetView: true,
            /**
             * Expects a cookie class constructor. Rarely changed, but you could change this to use another cookie class. This must adhear to the cookie interface used.
             * @type {object}
             * @default new Cookies()
             * @memberOf storeLocator.pars
             * @name cookies
             */
            cookies: new Cookies(),
            /**
             * Height of the map when results pane is shown <br/>
             * This is used for sending to the server, the code does NOT use these values to resize the map
             * @type {int}
             * @memberOf storeLocator.pars
             * @name resultsMapHeight
             */
            resultsMapHeight: 662,
            /**
             * Width of map when the results pane is shown. <br/>
             * This is used for sending to the server, the code does NOT use these values to resize the map
             * @type {int}
             * @memberOf storeLocator.pars
             * @name resultsMapWidth
             */
            resultsMapWidth: 672
        }
    },
    /**
     * Grabs the storeLocatorConfig object
     *
     * @this {setConfig}
     * @returns {object} the storeLocatorConfig object to save inside the storeLocator namespace
     */
    setConfig:function(){
    	return storeLocatorConfig;
    },

    /**
    * Sets up the Base Variables for use inside the storeLocator Code
    * @private
    */
    setVars: function(){
        return {
            /**
             * Holds the address entered by the user
             * @type {string}
             * @default false
             * @memberOf storeLocator.vars
             * @name address
             */
            address: false,
            /**
             * jSON object used to cache the service responses
             * @type {object}
             * @default {}
             * @memberOf storeLocator.vars
             * @name cache
             */
            cache: {},
            /**
             * Retains the current page in the store results slider
             * @type {boolean}
             * @default false
             * @memberOf storeLocator.vars
             * @name currentPage
             */
            currentPage: false,
            /**
             * If 'inline' directions are used this object holds the GDirections object
             * @type {object}
             * @default false
             * @memberOf storeLocator.vars
             * @name directions
             */
            directions: false,
            /**
             * If 'inline' directions are used this object holds GEvents for the GDirections object
             * @type {object}
             * @default \{\}
             * @memberOf storeLocator.vars
             * @name directionEvents
             */
            directionEvents: {},
            /**
             * Indicates if this is the first load of the app so we can run initial setup functions
             * @type {boolean}
             * @default true
             * @memberOf storeLocator.vars
             * @name initialLoad
             */
            initialLoad: true,
            /**
             * Holds the GMap object
             * @type {object}
             * @default false
             * @memberOf storeLocator.vars
             * @name map
             */
            map: false,
            /**
             * Holds the lat and lon for the mapCenter
             * @type {boolean}
             * @default false
             * @memberOf storeLocator.vars
             * @name mapCenter
             */
            mapCenter: false,
            /**
             * Holds the last mapBounds returned by the service. <br/>
             * This is for use with the filters as we return the map bounds to the service when filtering.
             * @type {boolean}
             * @default false
             * @memberOf storeLocator.vars
             * @name mapBounds
             */
            mapBounds: false,
            /**
             * Holds the currently selected searchDistance
             * @type {int}
             * @default parseInt(storeLocator.config.radius.defaultSearchDistance)
             * @memberOf storeLocator.vars
             * @name radius
             */
            radius: parseInt(storeLocator.config.radius.defaultSearchDistance),
            /**
             * The drawn radius overlay object
             * @type {object}
             * @default false
             * @memberOf storeLocator.vars
             * @name radiusOverlay
             */
            radiusOverlay: false,
            /**
             * The height of a store record, used for calculating the area height for the rec slider
             * @type {int}
             * @default false
             * @memberOf storeLocator.vars
             * @name recHeight
             */
            recHeight: false,
            /**
             * Temporarly holds the store results from the web service
             * @type {object}
             * @default false
             * @memberOf storeLocator.vars
             * @name results
             */
            results: false,
            /**
             * Store types currently selected by the user to display
             * @type {string{}}
             * @default false
             * @memberOf storeLocator.vars
             * @name selectedStores
             */
            selectedStores: false,
            /**
             * Flag for showing the "miles to" label in the results list
             * @type {boolean}
             * @default true
             * @memberOf storeLocator.vars
             * @name showDistance
             */
            showDistance: true,
            /**
             * Holds two street view objects GStreetviewOverlay GStreetviewPanorama
             * @type {}
             * @default {pano:false, overlay:false}
             * @memberOf storeLocator.vars
             * @name streetView
             */
            streetView: {
                pano: false,
                overlay:false
            },
            /**
             * Holds the instances of the store markers
             * @type {}
             * @default []
             * @memberOf storeLocator.vars
             * @name stores
             */
            stores: [],
            /**
             * Temporarly stores the store results for later use
             * @type {}
             * @default null
             * @memberOf storeLocator.vars
             * @name storeObj
             */
            storeObj: null,
            /**
             * The total amount of pages for the slider
             * @type {}
             * @default false
             * @memberOf storeLocator.vars
             * @name totalPages
             */
            totalPages: false,
            /**
             * The current latitude. This updates when a user does a pan, or when pulling from the cache
             * @type {}
             * @default storeLocator.config.defaultLat
             * @memberOf storeLocator.vars
             * @name lat
             */
            lat: storeLocator.config.defaultLat,
            /**
             * The current longitude. This updates when a user does a pan, or when pulling from the cache
             * @type {}
             * @default storeLocator.config.defaultLon
             * @memberOf storeLocator.vars
             * @name lon
             */
            lon: storeLocator.config.defaultLon,
            /**
             * The current zoom level
             * @type {}
             * @default 10
             * @memberOf storeLocator.vars
             * @name zoom
             */
            zoom: 10,
            /**
	     * Client Geo Coder
	     * @type {}
	     * @default null
	     * @memberOf storeLocator.vars
	     * @name clientGeocoder
	     */
            clientGeocoder:null
            
        }
    },

    /* Templates */
    setTemplates: function(){
        return {
            /**
             * This is for the main content of the page. Any thing that is between the header and footer
             * @type {}
             * @default $('#storelocator-body').html()
             * @memberOf storeLocator.templates
             * @name mainContent
             */
            mainContent: $('#storelocator-body').html(),
            /**
             * Template is rendered when user trys to do a search with no address entered
             * @type {}
             * @default storeLocator.config.labels.blankSearch
             * @memberOf storeLocator.templates
             * @name blankSearch
             */
            blankSearch: storeLocator.config.labels.blankSearch,
            /**
             * Content for the welcome header message that is displayed in the results list pane
             * @type {}
             * @default ''
             * @memberOf storeLocator.templates
             * @name welcomeHeader
             */
            welcomeHeader: '',
            /**
             * Content for the welcome message that is displayed in the results list pane
             * @type {}
             * @default <p>{{ this.config.labels.welcomeMessage }}</p>
             * @memberOf storeLocator.templates
             * @name welcomeRecList
             */
            welcomeRecList: '<p>{{ this.config.labels.welcomeMessage }}</p>',
            /**
             * Shell content of the results list
             * @type {}
             * @default contains html which doesn't render here
             * @memberOf storeLocator.templates
             * @name results
             */
            results: '<h2 id="result-header"></h2><div id="result-list"></div><div id="result-footer"></div>',
            /**
             * Content for the result header
             * @type {}
             * @default {% if (this.count == 1) { %} {{ this.config.labels.oneStore.pre }} {% } else { %} {{ this.config.labels.multipleStores.pre }}  {% } %} {{this.count}} {% if (this.count != 1) { %}{{ this.config.labels.multipleStores.post }} {% }else{ %} {{ this.config.labels.oneStore.post }} {% } %}
             * @memberOf storeLocator.templates
             * @name resultHeader
             */
            resultHeader: '{% if (this.count == 1) { %} {{ this.config.labels.oneStore.pre }} {% } else { %} {{ this.config.labels.multipleStores.pre }}  {% } %} {{this.count}} {% if (this.count != 1) { %}{{ this.config.labels.multipleStores.post }} {% }else{ %} {{ this.config.labels.oneStore.post }} {% } %}',
            /**
             * Template to render each store in the results pane
             * @type {}
             * @default $('#template-result-store').html()
             * @memberOf storeLocator.templates
             * @name resultStore
             */
            resultStore: $('#template-result-store').html(),
            /**
             * Content of the result footer
             * @type {}
             * @default $('#template-result-footer').html()
             * @memberOf storeLocator.templates
             * @name resultFooter
             */
            resultFooter: $('#template-result-footer').html(),
            /**
             * Content to be displayed in the results list when an error occures
             * @type {}
             * @default '<div class="error">{{this.ERRORS.title}}<p>{{this.ERRORS.message}}</p></div>'
             * @memberOf storeLocator.templates
             * @name resultListError
             */
            resultListError: '<div class="error">{{this.ERRORS.title}}<p>{{this.ERRORS.message}}</p></div>',
            /**
             * Content to be displayed in the popup when an error occures
             * @type {}
             * @default
             * @memberOf storeLocator.templates
             * @name resultErrorBox
             */
            resultErrorBox: '{{this.ERRORS.title}}',
            /**
             * Map tooltip content
             * @type {}
             * @default $('#template-bubble').html()
             * @memberOf storeLocator.templates
             * @name bubble
             */
            bubble: $('#template-bubble').html(),
            /**
             * Template to be rendered to return the user back to results when viewing inline directions
             * @type {}
             * @default '<div id="return-to-search">{{this.config.labels.backToResults}}</div>'
             * @memberOf storeLocator.templates
             * @name backToResults
             */
            backToResults: '<div id="return-to-search">{{this.config.labels.backToResults}}</div>'
        }
    },

    /**
     * Called on clicking the directions element
     * Can be overridden per implementation if needed
     */
    getDirections: function(el) {
        storeLocator.directions.getInstanceOf(storeLocator.pars.directionsType, el)
    },

    /**
     * Utility to extend the object sent to a template with the config data from the service, giving the template access to all config options
     */
    compileData: function(d){
        return u.extend(d, {config:storeLocator.config})
    },

    /**
     * Runs intial setup of store locator.
     */
    init: function(){
        /**
         * Configuration object obtained from the store locator config service
         */
        storeLocator.config = storeLocator.setConfig()

        /**
         * Configurable parameter object for store locator
         * @namespace
         */
        storeLocator.pars = storeLocator.setPars()
        /**
         * Internal variables used by store locator
         * @namespace
         */
        storeLocator.vars = storeLocator.setVars()
        /**
         * Holds references to the templates used by store locator
         * @namespace
         */
        storeLocator.templates = storeLocator.setTemplates()
        $('#body').append(storeLocator.templates.mainContent.uTemplate(storeLocator))
        $('#sl-main-loader').hide()
        $(document.body).mousemove(Loader.follow);
        storeLocator.map.setup('#map')

        if(storeLocator.pars.defaultToolTips)
            $.tooltip(storeLocator.pars.defaultToolTips.selector, storeLocator.pars.defaultToolTips.pars)

        $.tooltip('.store-type-tooltip-controller',{
            tipPosition: 'left center',
            anchorPosition: 'right center',
            tip: '<div class="store-type-tooltip"></div>'
        })
    },

    /**
     * Stops the form submission when user presses the enter key in the address field. <br/>
     * Runs the AJAX search <br/>
     * Fires sl:store-locator-search
     * @event
     */
    stopReturn: function(e) {
        if (e.keyCode == 13) {
            storeLocator.updateMap();
            storeLocator.fire('sl:store-locator-search')
            return false;
        }
    },

    /**
     * Clears all errors, results, and markers <br/>
     * Called on every action that repaints the view with new data
     */
    resetPage: function(badAddress) {
        // Clear points
        storeLocator.map.clearMarkers(storeLocator.vars.map,storeLocator.vars.stores)
        storeLocator.vars.currentPage = 1

        $('#result-header, #result-list, #result-footer, #error-box, #directions').html('');
        $('#search-area, #control-row, #store-locator').removeClass('directions')
        if(!badAddress){
            $('#store-locator').addClass('searching')
        }
        $('#error-box').hide()
        storeLocator.map.bubble.hide()
        storeLocator.fire('close-street-view')
    },

    /**
     * Displays the error popup with the message passed to it <br/>
     * Fires Event: storeLocator.fire('error-event', {msg:msg})
     * @param {object} msg
     *
     */
    msg: function(msg) {
        storeLocator.fire('error-event', {msg:msg})
        $('#error-box').css('display','block');
        $('#error-box').html(msg);
    },

    /**
     * Builds the address to be sent to the service. This includes the radius value
     * @returns {string} returns address string
     */
    buildAddress: function() {
        var string = '';

        if(storeLocator.config.radius.enabled && storeLocator.config.radius.options.length > 0)
            storeLocator.vars.radius = parseFloat($('#store-search-radius').val())

        var address = $('#store-search-street').val().uStrip();

        if (address){
            string += address;
        } else {
            storeLocator.resetPage(true);
            storeLocator.msg(storeLocator.templates.blankSearch);
            $('#result-list').html(storeLocator.templates.resultListError.uTemplate({ERRORS:{title:'', message:storeLocator.templates.blankSearch}}));
            $('#hide-results').hide()
            $('#store-locator').removeClass('searching')
        }
        return string;
    },

    /**
     * Clears GEvent listeners <br/>
     * Verifies the browser is compatible <br/>
     * Starts of Map Update Cascade for searching by address <br/>
     * Fires Event: storeLocator.fire('get-stores', {t:'s'})
    */
    updateMap: function() {
        GEvent.clearInstanceListeners(storeLocator.vars.map)
        storeLocator.vars.map.clearOverlays()
        storeLocator.vars.directions.clear()

        if (GBrowserIsCompatible()) {
            $('#directions').hide()
            //clear bubble
            storeLocator.map.bubble.hide()

            //populate global pars object
            storeLocator.vars.address = storeLocator.buildAddress()
            if(!storeLocator.vars.address){
                return
            }
            storeLocator.fire('get-stores', {t:'s'})
        }
    },

    /**
     * Updates map view to the coordinates supplied by the service <br/>
     * Save the new position
     */
    moveMapToBounds: function(point){
        var sw = new GLatLng(point.SouthLatitude,point.WestLongitude)
        var ne = new GLatLng(point.NorthLatitude,point.EastLongitude)
        var bounds = new GLatLngBounds(sw,ne)
        storeLocator.vars.mapCenter = bounds.getCenter()
        storeLocator.vars.zoom = storeLocator.vars.map.getBoundsZoomLevel(bounds)
        storeLocator.vars.lat = storeLocator.vars.mapCenter.y
        storeLocator.vars.lon = storeLocator.vars.mapCenter.x
        storeLocator.vars.map.setCenter(new GLatLng(storeLocator.vars.lat, storeLocator.vars.lon), storeLocator.vars.zoom);
        storeLocator.vars.map.savePosition()
    },

    /**
     * Sets up the various location markers
     */
    setLocation: function(point, cached) {

        GEvent.clearInstanceListeners(storeLocator.vars.map)

        setTimeout(function() { storeLocator.events.map(storeLocator.vars.map) }, 100);

        if(storeLocator.vars.initialLoad == true){
            storeLocator.map.cachedSetup(storeLocator.vars.map,false)
            storeLocator.vars.initialLoad = false
            storeLocator.vars.map.savePosition()
        }

        storeLocator.drawRadiusOverlay()
        storeLocator.setCookie();
        storeLocator.showList()
        storeLocator.map.adjustZoomButtons(storeLocator.vars.map)
    },

    /**
     * Gets the current center point after a user pans <br/>
     * Fires Event: storeLocator.fire('get-stores', {t:'p'})
     */
    getFromHere: function(hd) {
        if(hd) {
            storeLocator.vars.showDistance = false
        }
        storeLocator.vars.mapCenter = storeLocator.vars.map.getCenter();
        //set new points
        storeLocator.vars.lat = storeLocator.vars.mapCenter.lat()
        storeLocator.vars.lon = storeLocator.vars.mapCenter.lng()
        storeLocator.fire('get-stores', {t:'p'})
        storeLocator.map.buildZoomSlider()
    },

    /**
     * Checks if radius overlay is enabled and adds it to the map
     */
    drawRadiusOverlay: function(){
        if (storeLocator.config.radius.draw && storeLocator.config.radius.enabled) {
            if (!storeLocator.vars.mapCenter) return;
            storeLocator.removeRadiusOverlay()
            storeLocator.vars.radiusOverlay = new Radius({radius:storeLocator.vars.radius, lat:storeLocator.vars.mapCenter.lat(), lng:storeLocator.vars.mapCenter.lng(), unitMeasure:storeLocator.config.unitMeasure})
            storeLocator.vars.map.addOverlay(storeLocator.vars.radiusOverlay);
        }
    },

    /**
     * Removes the radius overlay
     */
    removeRadiusOverlay: function(){
        if (storeLocator.vars.radiusOverlay) {
            storeLocator.vars.map.removeOverlay(storeLocator.vars.radiusOverlay);
            storeLocator.vars.overlay = null;
        }
    },

    /**
     * Sets the following store locator cookies: <br/>
     * storeLocator-lat <br/>
     * storeLocator-lng <br/>
     * storeLocator-zoom <br/>
     * storeLocator-address <br/>
     * storeLocator-radius
     */
    setCookie: function() {
        if($('#store-search-street').val().uStrip()){
            // Store the new points
            storeLocator.pars.cookies.setCookie('storeLocator-lat', escape(storeLocator.vars.lat), '/', storeLocator.config.cookieMaximumAge)
            storeLocator.pars.cookies.setCookie('storeLocator-lng', escape(storeLocator.vars.lon), '/', storeLocator.config.cookieMaximumAge)
            storeLocator.pars.cookies.setCookie('storeLocator-zoom', escape(storeLocator.vars.zoom), '/', storeLocator.config.cookieMaximumAge)
            storeLocator.pars.cookies.setCookie('storeLocator-address', escape($('#store-search-street').val().uStrip()), '/', storeLocator.config.cookieMaximumAge)
            storeLocator.pars.cookies.setCookie('storeLocator-radius', escape($('#store-search-radius').val()), '/', storeLocator.config.cookieMaximumAge)
        }
    },

    /**
     * Grabs the current selected store filters
     * @returns {string} 'filter1,filter2'
     */
    getSelectedStoreTypes: function(){
        var tags = new Array
        $.each($('input[class*=store-selector]:checked'),function(i,v){
            tags.push($(v).val())
        })
        //Save selected tags for checking cache
        storeLocator.vars.selectedStores = tags.toString()
        return tags;
    },

    /**
     * Sets variables based on data returned <br/>
     * Fires Event: storeLocator.fire('service-results-parsed', data)
     * @param {object} data Where data is the jSON result from the store locator search service or the jSON saved in the cache
     */
    parseServiceResponse:  function(data){
        storeLocator.vars.mapBounds = data.MAPBOUNDS
        storeLocator.vars.results = data
        storeLocator.setLocation()
        storeLocator.fire('service-results-parsed', data)
        Loader.off()
    },

    /**
     * Called after a successful address search <br/>
     * Calls storeLocator.moveMapToBounds and storeLocator.parseServiceResponse
     * @see storeLocator.parseServiceResponse
     * @see storeLocator.moveMapToBounds
     */
    processSearchResponse: function(e){
        storeLocator.moveMapToBounds(e.message.data.MAPBOUNDS);
        storeLocator.parseServiceResponse(e.message.data)
        storeLocator.clearServiceEvents()
    },

    /**
     * Called after a successful pan service call <br/>
     * Calls storeLocator.parseServiceResponse
     * @see storeLocator.parseServiceResponse
     */
    processPanResponse: function(e){
        storeLocator.parseServiceResponse(e.message.data)
        storeLocator.clearServiceEvents()
    },

    /**
     * Called after a successful IPGeolocation service call <br/>
     * Calls storeLocator.moveMapToBounds and storeLocator.parseServiceResponse
     * @see storeLocator.parseServiceResponse
     * @see storeLocator.moveMapToBounds
     */
    processIPGeolocationResponse: function(e){
        if (e.message.data.COUNT > 0 || e.message.data.ERRORS){
            storeLocator.moveMapToBounds(e.message.data.MAPBOUNDS);
            storeLocator.parseServiceResponse(e.message.data)
        }else{
            storeLocator.moveMapToBounds(e.message.data.MAPBOUNDS)
        }
        storeLocator.clearServiceEvents()
    },

    /**
     * Build the service call parameters for each search type and sets up the appropriate event listeners
     * @param t Search type. Accepts 's' for search, 'p' for pan, 'f' for filter. if passed any other value the default runs
     * @returns {object} Service call parameters
     */
    buildParams: function(t){
        var r = {
            StoreTag:storeLocator.getSelectedStoreTypes(),
            Height: $('#map').height(),
            Width: $('#map').width()
        }

        switch(t){
            case 's':
                storeLocator.observe('results-loaded', storeLocator.toggleResultsPane)
                storeLocator.observe('service-call-complete', storeLocator.processSearchResponse)
                r.Address = storeLocator.vars.address
                r.Height = storeLocator.pars.resultsMapHeight
                r.Width = storeLocator.pars.resultsMapWidth
                if(storeLocator.config.radius.enabled)
                    r.Radius = storeLocator.vars.radius

                break;
            case 'p':
                storeLocator.observe('service-call-complete', storeLocator.processPanResponse)
                u.extend(r, storeLocator.getMapCorners())
                if(storeLocator.vars.showDistance){
                    r.Address = storeLocator.vars.address
                }
                break;
            case 'f':
                storeLocator.observe('service-call-complete', storeLocator.processPanResponse)
                u.extend(r, storeLocator.vars.mapBounds)
                break;
            default:
                storeLocator.observe('results-loaded', storeLocator.toggleResultsPane)
                storeLocator.observe('service-call-complete', storeLocator.processIPGeolocationResponse)
                break;
        }

        return r;
    },

    /**
     *  @returns {object} Current coordinates of the map
     */
    getMapCorners: function(){
        // get the boundary of the currently displayed map
        var bounds = storeLocator.vars.map.getBounds();
        // get the location of the top right corner
        var topRight = bounds.getNorthEast();
        // get the location of the bottom left corner
        var bottomLeft = bounds.getSouthWest();
        return {
            // get the top boundary (north latitude)
            NorthLatitude:topRight.lat(),
            // get the bottom boundary (south latitude)
            SouthLatitude:bottomLeft.lat(),
            // get the right boundary (east longitude)
            EastLongitude:topRight.lng(),
            // get the left boundary (west longitude)
            WestLongitude:bottomLeft.lng()
        };
    },

    /**
     *  Checks the cache for the current request otherwise starts the service call
     *  @see storeLocator.checkCached
     *  @see storeLocator.callService
     */
    locatorService:function(e){
        var r = storeLocator.buildParams(e.message.t)
        var cache = storeLocator.checkCached(r)
        if(!cache){
            //added for client side geo-coding
            //if we have a valid address getting passed to ajax search controller, make sure to
            //geo-code at the client side
            if(r.Address){
              storeLocator.geoCodeAddressAndCallStoreLocatorService(r);
            }else{
              storeLocator.callService(r)
            }
        }else{
            storeLocator.fire('service-call-complete', {data:cache})
        }
    },
    
    /**
      *  Geocodes the address entered by shopper, ajax call to google maps api
      */
    geoCodeAddressAndCallStoreLocatorService:function(r){
        if (GBrowserIsCompatible()) {
            if(storeLocator.vars.clientGeocoder == null){
                storeLocator.vars.clientGeocoder = new GClientGeocoder();
                if(storeLocatorConfig.googleMapsRegion){
                    storeLocator.vars.clientGeocoder.setBaseCountryCode(storeLocatorConfig.googleMapsRegion);
                }
            }
            
            storeLocator.vars.clientGeocoder.getLatLng(
	        storeLocator.vars.address,
	        function(point) {
	            if(point){
	                r.Latitude =  point.lat();
	                r.Longitude = point.lng();
	            }else{
	                r.Latitude  = 0;
	                r.Longitude = 0;
	            }
	            storeLocator.callService(r);
	        }
            );
        }else{
            storeLocator.callService(r);
        }    
    },

    /**
     * Checks cache for current request
     * @returns {object|boolean} Either the cached data or false
     */
    checkCached: function(r){
        storeLocator.vars.cacheKey = $.param(r,true)
        if(storeLocator.vars.cache[storeLocator.vars.cacheKey]){
            return storeLocator.vars.cache[storeLocator.vars.cacheKey]
        }else{
            storeLocator.observe('service-results-parsed', storeLocator.setCache)
        }
        return false;
    },

    /**
     * Loads the current request into the cache
     */
    setCache: function(d){
        storeLocator.vars.cache[storeLocator.vars.cacheKey] = d.message
        storeLocator.stopObserving('service-results-parsed', storeLocator.setCache)
    },

    /**
     * Calls the store locator search service
     */
    callService: function(data){
        Loader.on()
        $.ajax({
            type: "GET",
            url: storeLocatorConfig.serviceURL,
            traditional: true,
            data: data,
            dataType: 'json',
            success:function(d){
                storeLocator.fire('service-call-complete', {data:d})
            },
            error: function(e){
                alert(e)
            }
        })
    },

    /**
     * Unregisters the service-call-complete events
     */
    clearServiceEvents: function(){
        storeLocator.stopObserving('service-call-complete', storeLocator.processSearchResponse)
        storeLocator.stopObserving('service-call-complete', storeLocator.processPanResponse)
        storeLocator.stopObserving('service-call-complete', storeLocator.processIPGeolocationResponse)
    },

    /**
     * Loads store results into the template and adds the template to the dom <br/>
     * Fires Event: storeLocator.fire('results-loaded')
     */
    showList: function () {
        // Reset current page vars
        var obj = storeLocator.vars.results
        storeLocator.resetPage(obj);

        // Save obj
        storeLocator.vars.storeObj = obj;

        if(obj.ERRORS){
            storeLocator.resetPage();
            storeLocator.msg(storeLocator.templates.resultErrorBox.uTemplate(obj));
            $('#result-list').html(storeLocator.templates.resultListError.uTemplate(obj));
            if(storeLocator.pars.openResultPaneErrorBox)
                storeLocator.toggleResultsPane()

            return;
        }

        // Just right amount of stores
        storeLocator.vars.totalPages = Math.ceil(parseInt(obj.COUNT)/storeLocator.config.resultsPerPage);

        // Number header
        storeLocator.list.updateStoreNumber(obj);

        // Record set
        var recSlider = new Array();
        recSlider.push('<div id="rec-slider" style="top: 0px;">');

        // For each store
        $.each(obj.RESULTS, function(i,v) {
            if (v.store) {
                if(storeLocator.pars.enableStreetView){
                    storeLocator.getStreetViewData(v)
                }
                v.index = i;
                storeLocator.list.getInstanceOf(v.store.storeType.typeId, v, recSlider)
            }
        });

        recSlider.push('</div>');
        $('#result-list').html(recSlider.join(''));
        // Buld the result footer
        var args = {};
        args.currentPage = storeLocator.vars.currentPage;
        args.totalPages = storeLocator.vars.totalPages;
        $('#result-footer').html(storeLocator.templates.resultFooter.uTemplate(storeLocator.compileData(args)));
        storeLocator.fire('results-loaded')
        storeLocator.list.setRecHeight()
    },

    /**
     * Hides or shows the results pane <br/>
     * Fires Event: storeLocator.fire('hide-results-pane') <br/>
     * Fires Event: storeLocator.fire('show-results-pane') <br/>
     * Unregisters Event: 'results-loaded'
     * @param s Map view type. 'map' = full map view/hide search results. any other value will show the results pane
     */
    toggleResultsPane: function(s){
        //ensue we always have the most recent center of the map
        storeLocator.vars.mapCenter = storeLocator.vars.map.getCenter();
        switch(s){
            case 'map':
                $('#results').hide()
                $('#hide-results').hide()
                $('#show-results').show()
                $('#map').addClass('full-map-view')
                storeLocator.vars.map.checkResize()
                storeLocator.vars.map.setCenter(storeLocator.vars.mapCenter)
                storeLocator.fire('hide-results-pane')
                break;
            default:
                $('#results').show()
                $('#hide-results').show()
                $('#show-results').hide()
                $('#map').removeClass('full-map-view')
                storeLocator.vars.map.checkResize()
                storeLocator.vars.map.setCenter(storeLocator.vars.mapCenter)
                storeLocator.fire('show-results-pane')
                break;
        }
        storeLocator.stopObserving('results-loaded', storeLocator.toggleResultsPane)
    },

    /**
     * Gets google streetview data and sets the data in the store object
     * @param v Store object
     */
    getStreetViewData: function(v){
        storeLocator.vars.streetView.client.getNearestPanoramaLatLng(new GLatLng(v.store.latitude, v.store.longitude), function(d){
            v.store.streetViewData = d
        },v)
    },

    /**
     * Loads street view data and displays the overlay
     */
    getStreetView: function(e){
        var v = u.templateUtils.getData(e.message.item)
        storeLocator.map.bubble.hide()
        storeLocator.vars.streetView.pano.setLocationAndPOV(new GLatLng(v.store.latitude, v.store.longitude))

        $('#pano').show()
        $('#store-locator').addClass('street-view')
        $('#store-locator').removeClass('searching')
    },

    /**
     * Closes the street view overlay
     */
    closeStreetView: function(e){
        storeLocator.vars.streetView.pano.remove()
        $('#pano').hide().html('')
        $('#store-locator').removeClass('street-view')
        $('#store-locator').addClass('searching')
    }
}

/**
 * Interaction Events: setup and handlers
 * @namespace
 */
storeLocator.events = {
    /**
     * Registers interaction events and optional street view events
     */
    setup: function() {

        if(storeLocator.pars.enableStreetView){
            storeLocator.observe('get-street-view', function(e){
                storeLocator.getStreetView(e)
            })

            storeLocator.observe('close-street-view', function(e){
                storeLocator.closeStreetView(e)
            })

            $('.get-street-view').live('click', function(e){
                storeLocator.fire('get-street-view', {item:e.target, e:e})
            })

            $('#close-street-view').live('click', function(e){
                storeLocator.fire('close-street-view', {e:e})
            })
        }

        $('#store-search-submit').click(function(e) {
            e.preventDefault()
            storeLocator.vars.showDistance = true
            storeLocator.vars.lat = ''
            storeLocator.vars.lon = ''
            storeLocator.updateMap();
            if(storeLocator.pars.autoCloseWelcome)
                $('#welcome-msg-container').hide()

            storeLocator.fire('sl:store-locator-search')
        });

        $('#store-search-street').keydown(storeLocator.stopReturn)

        $('#return-to-search').live('click', storeLocator.map.getFromCookie)

        $('#controls dd').live('click', function(e){
            storeLocator.events.controls(e);
        })

        $('#map-bubble dt').live('click', function(e){
            storeLocator.events.mapBubble(e);
        })

        $('#prev-stores').live('click', function(e){
            storeLocator.list.pan(storeLocator.vars.currentPage - 2);
        })

        $('#next-stores').live('click', function(e){
            storeLocator.list.pan(storeLocator.vars.currentPage);
        })

        $('#result-list .hours').live('click', function(e){
            storeLocator.events.resultList(e);
        })

        $('.get-directions').live('click', function(e){
            storeLocator.getDirections(e.target)
        })

        $('.close-welcome-msg').live('click', function(e){
            $('#welcome-msg-container').hide()
        })

        $('#hide-results').live('click', function(e){
            storeLocator.toggleResultsPane('map')
        })

        $('#show-results').live('click', function(e){
            storeLocator.toggleResultsPane()
        })

        $('input[class*=store-selector]').live('click', function(e){
            storeLocator.fire('get-stores', {t:'f'})
            storeLocator.fire('store-type-clicked', {item:e.target, e:e})
        })
    },

    /**
     * Registers GEvents on the map <br/>
     * Fires Event: storeLocator.fire('map-events-registered')
     */
    map: function(map) {
        GEvent.addListener(map, "dragstart", function() {
            storeLocator.vars.showDistance = false;
        });

        storeLocator.vars.panEvent = GEvent.addListener(map, "moveend", storeLocator.getFromHere)

        GEvent.addListener(map, "movestart", function() {
            // Clear bubble
            storeLocator.map.bubble.hide();
        });

        storeLocator.fire('map-events-registered')
    },

    /**
     *  Inline directions events hanlder
     */
    directions:{
        /**
         * Adds inline directions event hanlders
         */
        addEvents: function(){
            storeLocator.vars.directionEvents.moveend = GEvent.addListener(storeLocator.vars.map, 'moveend', function(e){
                storeLocator.resetPage()
                storeLocator.removeRadiusOverlay()
                $('#store-locator').removeClass('searching')
                $('#search-area, #control-row, #store-locator').addClass('directions')
                storeLocator.map.bubble.hide()
                storeLocator.events.directions.removeEvents()
                storeLocator.map.adjustZoomButtons(storeLocator.vars.map)
                storeLocator.vars.map.savePosition()
                $('#directions').show().prepend(storeLocator.templates.backToResults.uTemplate(storeLocator))
                storeLocator.toggleResultsPane('map')
                $('#show-results').hide()
            })

            storeLocator.vars.directionEvents.error = GEvent.addListener(storeLocator.vars.directions, 'error', function(e){
                storeLocator.map.bubble.hide()
                storeLocator.events.directions.removeEvents()
                storeLocator.msg(storeLocator.templates.resultErrorBox.uTemplate({ERRORS:{title:storeLocator.config.errorCodes[e.getStatus().code]}}));
                storeLocator.vars.panEvent = GEvent.addListener(storeLocator.vars.map, "moveend", storeLocator.getFromHere)
            })
        },
        /**
         * Removes inline directions event handlers
         */
        removeEvents:function(){
            GEvent.removeListener(storeLocator.vars.directionEvents.moveend)
            GEvent.removeListener(storeLocator.vars.directionEvents.error)
        }
    },

    /**
     * Handler for the controls
     * @param e Click Event
     */
    controls: function(e) {
        storeLocator.map.bubble.hide();
        var map = storeLocator.vars.map;

        switch (e.target.id) {
            case 'pan-up':
                map.panDirection(0,+1);
                storeLocator.vars.showDistance = false;
                storeLocator.fire('pan', {dir:'up', e:e})
                break;
            case 'pan-down':
                map.panDirection(0,-1);
                storeLocator.vars.showDistance = false;
                storeLocator.fire('pan', {dir:'down', e:e})
                break;
            case 'pan-left':
                map.panDirection(+1,0);
                storeLocator.vars.showDistance = false;
                storeLocator.fire('pan', {dir:'left', e:e})
                break;
            case 'pan-right':
                map.panDirection(-1,0);
                storeLocator.vars.showDistance = false;
                storeLocator.fire('pan', {dir:'right', e:e})
                break;
            case 're-center':
                storeLocator.vars.showDistance = true;
                map.returnToSavedPosition();
                storeLocator.vars.zoom = map.getZoom();
                $("#slider").slider('value', storeLocator.vars.zoom)
                storeLocator.fire('center',{e:e})
                break;
            case 'zoom-in':
                storeLocator.map.zoom(map,+1);
                storeLocator.fire('zoom', {dir:'in', e:e})
                break;
            case 'zoom-out':
                storeLocator.map.zoom(map,-1);
                storeLocator.fire('zoom', {dir:'out', e:e})
                break;
            case 'cmap':
                storeLocator.map.mapType(map,0);
                storeLocator.fire('change-map-type', {dir:'cmap', e:e})
                break;
            case 'csat':
                storeLocator.map.mapType(map,1);
                storeLocator.fire('change-map-type', {dir:'csat', e:e})
                break;
            case 'chybrid':
                storeLocator.map.mapType(map,2);
                storeLocator.fire('change-map-type', {dir:'chybrid', e:e})
                break;
        }
    },

    /**
     * Handles click events inside the map tooltip
     * @param e Click Event
     */
    mapBubble: function(e) {
        var el = $(e.target)
        if (el.hasClass('close')) {
            storeLocator.map.bubble.hide();
        } else {
            if(!el.hasClass('active')){
                el.parent().children('.active').removeClass('active')
                el.addClass('active')
                el.next().addClass('active')
            }
        }
    },

    /**
     * Handles click events in the results list for the store objects
     * @param e Click Event
     */
    resultList: function(event) {
        var store = $(event.target).parents('.store')[0];
        var index = store.id.replace('store-list-','');
        var marker = storeLocator.vars.stores[index];
        var v = storeLocator.vars.storeObj.RESULTS[index];
        storeLocator.map.bubble.show(index,v,'#'+store.id,marker,false);
        $('#map-bubble .info').addClass('active');
        $('#map-bubble .hours').removeClass('active');
    }
}

/**
 * Map specific functions
 * @namespace
 */
storeLocator.map = {
    /**
     * Initializes the map display, inline directions, and street view <br/>
     * Registers Interaction Event Handlers
     * @see storeLocator.events.setup
     * @see storeLocator.map.initialSetup
     * @see storeLocator.map.cachedSetup
     * @param map css selector for the map element
     */
    setup: function(map) {
        map = storeLocator.vars.map = new GMap2($(map)[0]);
        storeLocator.vars.directions = new GDirections(storeLocator.vars.map,document.getElementById(storeLocator.pars.directionsContainerID))

        if(storeLocator.pars.enableStreetView){
            storeLocator.vars.streetView.overlay = new GStreetviewOverlay()
            storeLocator.vars.streetView.pano = new GStreetviewPanorama(document.getElementById("pano"));
            storeLocator.vars.streetView.client = new GStreetviewClient()

            GEvent.addListener(storeLocator.vars.streetView.pano, "error", function(e){
                storeLocator.closeStreetView()
                if(e == 603){
                    storeLocator.msg(storeLocator.config.labels.streetViewFlashError)
                }else{
                    storeLocator.msg(storeLocator.config.labels.streetViewError)
                }
            });
        }

        // Get rid of bubble shadow
        map.getPane(G_MAP_FLOAT_SHADOW_PANE).style.display = 'none'

        // Observe events
        storeLocator.events.setup();

        if (storeLocator.vars.initialLoad == true) {
            storeLocator.map.initialSetup(map);
        } else {
            storeLocator.map.cachedSetup(map,true,false);
        }
    },

    /**
     * Updates the map display to the intial requested view: <br/>
     * Using query params, cookie data, ipgeolocation, or default bounds sent from the config service<br/>
     * @param map GMap object
     */
    initialSetup: function(map) {
        storeLocator.observe('get-stores', storeLocator.locatorService)
        var query = location.href.u_toQueryParams()
        if (query.address != null){
            storeLocator.vars.address = query.address;
            $('#store-search-street').val(storeLocator.vars.address);
            if(parseInt(query.zoom)){
                storeLocator.vars.queryZoom = parseInt(query.zoom)
                storeLocator.vars.setZoomEvent = storeLocator.observe('map-events-registered', storeLocator.map.zoomTo)
            }
            storeLocator.fire('get-stores', {t:'s'})
        }else if (query.lat != null && query.lon != null) {
            if(parseInt(query.zoom)){
                storeLocator.vars.zoom = parseInt(query.zoom)
            }
            storeLocator.observe('results-loaded', storeLocator.toggleResultsPane)
            storeLocator.map.cachedSetup(map,true,true);
        }else if (storeLocator.map.getFromCookie()){
            // getFromCookie does all the leg work
        }else if (storeLocator.config.isIpGeoCodingEnabled){
            storeLocator.fire('get-stores', {t:'ip'})
        }else if (storeLocator.config.defaultMapBounds) {
            storeLocator.moveMapToBounds(storeLocator.config.defaultMapBounds)
        }else{
            map.setCenter(new GLatLng(storeLocator.vars.lat, storeLocator.vars.lon), 3);
        }
        if (storeLocator.vars.initResultList) {
            $('#result-header').html(storeLocator.templates.welcomeHeader);
            $('#result-list').html(storeLocator.templates.welcomeRecList);
        }
    },

    /**
     * Trys to retrieve save search data from cookies and stores the data in storeLocator.vars
     * @returns {boolean}
     */
    getFromCookie: function(){
        window.scrollTo(0,0)
        var lat = storeLocator.pars.cookies.getCookie('storeLocator-lat')
        var lon = storeLocator.pars.cookies.getCookie('storeLocator-lng')
        var address = storeLocator.pars.cookies.getCookie('storeLocator-address')
        if(lat && lon || address){
            storeLocator.vars.lat = lat
            storeLocator.vars.lon = lon
            storeLocator.vars.address = address
            storeLocator.vars.zoom = storeLocator.pars.cookies.getCookie('storeLocator-zoom')
            var r = storeLocator.pars.cookies.getCookie('storeLocator-radius')
            if(storeLocator.config.radius.enabled && r && r != "undefined"){
                storeLocator.vars.radius = parseFloat(r)
                $('#store-search-radius').val(r).change()
            }
            $('#store-search-street').val(storeLocator.vars.address)
            storeLocator.updateMap()
            $('#welcome-msg-container').hide()
            return true;
        }else{
            return false;
        }

    },

    /**
     * Add map controls and inits events
     * @param map GMap object
     * @param init {boolean} Flag for running initialization of events
     * @param pdp {boolean} Flag for setting up map display if a lat and lon were found in the query params
     */
    cachedSetup: function(map,init,pdp) {
        if (pdp) {
            map.setCenter(new GLatLng(location.href.u_toQueryParams().lat, location.href.u_toQueryParams().lon), storeLocator.vars.zoom);
            storeLocator.getFromHere();
        }

        // Clear results
        $('#results').html(storeLocator.templates.results);

        storeLocator.map.addMapControls(map);

        if (init) {
            storeLocator.events.map(map);
            var point = new GLatLng(storeLocator.vars.lat,storeLocator.vars.lon);
        }
    },

    /**
     * Displays the map controls <br/>
     * Hides the welcome message <br/>
     * Sets up the Zoom slider
     * @param map GMap object
     */
    addMapControls: function(map) {
        if(storeLocator.pars.autoCloseWelcome)
            $('#welcome-msg-container').hide()

        $('#control-row').addClass('active');
        $('#store-locator').addClass('searching')
        // Adjust map type
        storeLocator.map.adjustMapButtons(storeLocator.vars.map);

        //record set
        var recSlider = $('<div id="recSlider"></div>')
        recSlider.css('top', '0px')

        storeLocator.map.buildZoomSlider()
    },

    buildZoomSlider: function(){
        storeLocator.vars.map.getCurrentMapType().getMaxZoomAtLatLng(new GLatLng(storeLocator.vars.mapCenter.y,storeLocator.vars.mapCenter.x), function(r){
            var sliderPars = {
                min:parseInt(storeLocator.config.minZoomLevel),
                max:parseInt(storeLocator.config.maxZoomLevel),
                value: storeLocator.vars.zoom,
                start: function(){
                    this.style.cursor = 'move'
                },
                stop: function(e, uid){
                    this.style.cursor = 'pointer'
                    storeLocator.vars.map.setZoom(uid.value)
                    storeLocator.map.adjustZoomButtons(storeLocator.vars.map)
                }
            }
            if (r && r['status'] == G_GEO_SUCCESS) {
                sliderPars.max = parseInt(r.zoom)
            }
            storeLocator.map.renderSlider(sliderPars)
        })
    },

    renderSlider: function(p){
        $('#slider').slider('destroy')
        $("#slider").slider(p);
        $('.slider a').css('margin-left', '-0.6em')
        storeLocator.map.adjustZoomButtons(storeLocator.vars.map)
    },

    /**
     * Removes all store markers from the map
     * @param map GMap object
     * @param stores Store object of all currently displayed stores
     */
    clearMarkers: function(map,stores) {
        if (stores.length>0) {
            while (stores.length>0) {
                map.removeOverlay(stores.shift());
            }
        }
    },

    /**
     * Zooms map and updates zoom buttons
     * @param {object} map GMap object
     * @param {int} dir Zoom direction
     * @see storeLocator.map.adjustZoomButtons
     */
    zoom: function(map,dir) {
        if(dir>0){
            if(storeLocator.config.maxZoomLevel>(parseInt(map.getZoom())))map.zoomIn()
        }else if(dir<0){
            if(storeLocator.config.minZoomLevel<(parseInt(map.getZoom())))map.zoomOut()
        }
        storeLocator.vars.zoom = map.getZoom()
        storeLocator.map.adjustZoomButtons(map)
    },

    /**
     * Determines what zoom buttons should be enabled and toggles them as such<br/>
     * Sets the value of the zoom slider to the current zoom level of the map
     * @param {object} map GMap object
     */
    adjustZoomButtons: function(map) {
        $('#zoom-out, #zoom-in').addClass('active');
        storeLocator.vars.zoom = map.getZoom();

        if (storeLocator.config.minZoomLevel>(storeLocator.vars.zoom-1)) {
            $('#zoom-out').removeClass('active');
        }
        else if(storeLocator.config.maxZoomLevel<(storeLocator.vars.zoom+1)){
            $('#zoom-in').removeClass('active');
        }

        $("#slider").slider('value', storeLocator.vars.zoom)
    },

    /**
     * Zooms the map to the level specified in the query params
     */
    zoomTo: function(){
        storeLocator.stopObserving('map-events-registered', storeLocator.map.zoomTo)
        storeLocator.vars.map.setZoom(storeLocator.vars.queryZoom)
        storeLocator.vars.queryZoom = false
    },

    /**
     * Set the map type (map, sat, hybrid)
     * @param {object} map GMap object
     * @param {int} i Map type value
     */
    mapType: function(map,i){
        map.setMapType(G_DEFAULT_MAP_TYPES[i]);
        storeLocator.map.adjustMapButtons(map);
    },

    /**
     * Set the currently active mapType button
     * @param {object} map GMap object
     */
    adjustMapButtons: function(map) {
        $('#cmap, #csat, #chybrid').removeClass('active');
        switch (map.getCurrentMapType().getName()) {
            case 'Map':
                $('#cmap').addClass('active');
                break;
            case 'Satellite':
                $('#csat').addClass('active');
                break;
            case 'Hybrid':
                $('#chybrid').addClass('active');
                break;
        }
    },

    /**
     * Find the pixel location of a store marker on the map
     * @param {object} store marker to find
     * @returns {object} x and y of the marker
     */
    markerToPixel: function(marker) {
        var par = false
        loopster:
        for(p in marker){
            try{
                if(marker[p].tagName == 'IMG'){
                    par = p
                    break loopster
                }
            }catch(e){}
        }
        if(!par){
            loopster2:
            for(p in marker){
                try{
                    if(marker[p].tagName == 'DIV'){
                            par = p
                            break loopster2
                    }
                }catch(e){}
            }
        }

        if(par){
            var pixels = $(marker[par]).offset()
            var pos = $('#map').offset()
            var x = parseInt(pixels.left) - parseInt(pos.left)
            var y = parseInt(pixels.top) - parseInt(pos.top)
        }else{
            var x = 0
            var y = 0
        }
        return {x:x,y:y}
    },

    /**
     * This may need to be removed as part of code cleanup
     * @ignore
     */
    index: function(ary,value) {
        loopster: for (var i=0; i<ary.length; i++) {
            if(ary[i] == value){
                return i;
                break loopster;
            }
        }
        return false;
    }
}


/**
 * Driving Directions Factory
 * @namespace
 */
storeLocator.directions = {
    /**
     * Grabs the instance of driving directions based on inline or popup
     * @param {string} t type of instance to return
     * @param {object} el store to get directions for
     */
    getInstanceOf: function(t, el){
        if(storeLocator.directions[t])
            return storeLocator.directions[t](el);
        else
            return storeLocator.directions.inline(el);
    },

    /**
     * Loads the driving directions into the dom element specified
     * @param {object} el store to get directions for
     * @see directionsContainerID
     */
    inline: function(el){
        storeLocator.fire('loading-inline-directions')
        storeLocator.events.directions.addEvents()
        GEvent.removeListener(storeLocator.vars.panEvent)
        var v = u.templateUtils.getData(el)
        var search
        if(storeLocator.vars.address != " "){
            search = 'from: '+ storeLocator.vars.address + ' to: ' + v.store.latitude +' '+ v.store.longitude
        }else{
            search = 'from: '+ storeLocator.vars.lat+' '+storeLocator.vars.lon + 'to: ' + v.store.latitude +' '+ v.store.longitude
        }

        storeLocator.vars.directions.clear()
        storeLocator.vars.directions.load(search)
    },

    /**
     *  Loads the driving directions in a popup window
     *  @param {object} el store to get directions for
     */
    popup: function(el){
        storeLocator.fire('loading-popup-directions')
        var v = u.templateUtils.getData(el)
        if(storeLocator.vars.address != " "){
            window.open('http://maps.google.com/maps?saddr='+storeLocator.vars.lat+','+storeLocator.vars.lon+'('+encodeURI(storeLocator.vars.address)+')&daddr='+v.store.latitude+','+v.store.longitude+'('+encodeURI(v.store.address1+' '+v.store.city+', '+v.store.stateCode+' '+v.store.postalCode)+')', "", "resizable=yes,scrollbars=yes,status=yes")
        }else{
            window.open('http://maps.google.com/maps?q='+v.store.latitude+','+v.store.longitude+'('+encodeURI(v.store.address1+' '+v.store.city+', '+v.store.stateCode+' '+v.store.postalCode)+')', "", "resizable=yes,scrollbars=yes,status=yes")
        }
    }
}

/**
 * Map Marker Factory
 *
 */
storeLocator.map.marker = {
    /**
     * Gets the instance of a map marker type
     * @param {string} t type of marker
     * @param {string} p url path to the marker image
     * @returns {object} GIcon for placement on the map
     */
    getInstanceOf: function(t, p) {
        if(storeLocator.map.marker[t])
            return storeLocator.map.marker[t](p);
        else
            return storeLocator.map.marker.base(p);
    },

    base: function(path){
        var icon = new GIcon();
        icon.image = path;
        icon.iconSize = new GSize(22, 28);
        icon.iconAnchor = new GPoint(7, 28);
        return icon
    }
}



/**
 * Map Bubble Factory
 */
storeLocator.map.bubble = {
    /**
     * Gets the instance of bubble type
     * @param {string} t type of bubble to be shown
     * @param {int} i position of store element in results list
     * @param {object} p store object
     * @returns {string} html element for placement on the map
     */
    getInstanceOf: function(t, i, p) {
        if(storeLocator.map.bubble[t])
            return storeLocator.map.bubble[t](i,p);
        else
            return storeLocator.map.bubble.base(i,p);
    },

    /**
     * Base buble type
     */
    base: function(i,p) {
        var bubbleString = storeLocator.templates.bubble;
        $(document.body).prepend(bubbleString.uTemplate(p));
        if(u.browser.IE6){
            var iframe = $('<iframe class="ie-bubble-blocker-frame" style="filter:progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0);" scrolling="no" src="javascript:false;" frameborder="0"></iframe>')
            $(document.body).prepend(iframe);
        }
        var bubble = $('#map-bubble');
        bubble.page = Math.floor(i/storeLocator.config.resultsPerPage);
        return bubble;
    },

    /**
     * Hides Map Bubble
     */
    hide: function() {
        storeLocator.list.goDim();
        if ($('#map-bubble')) {
            $('#map-bubble').remove();
            if($('.ie-bubble-blocker-frame')){
                $('.ie-bubble-blocker-frame').remove()
            }
        }
    },

    /**
     * Shows Map Bubble
     */
    show: function(i,v,rec,marker,pan) {
        $('#error-box').hide()
        // Hide Bubbles
        storeLocator.map.bubble.hide();


        // fire Store Locator Store Select
        storeLocator.fire('sl:store-locator-store-select', v.store.locationCode)

        // Get pixels
        var pixels = storeLocator.map.markerToPixel(marker);
        // Show bubble
        var bubble = storeLocator.map.bubble.getInstanceOf(v.store.storeType.typeId, i, u.extend(v, {showDistance:storeLocator.vars.showDistance}));

        // Position bubble: needs to be done after creating the bubble so we can get the width and height dynamically
        var left = pixels.x - $('#map-bubble').width()/2;
        var top = pixels.y - $('#map-bubble').height();
        var offset = $('#map').offset()

        bubble.css({
            left:left + offset.left + 'px',
            top:top + offset.top + 'px'
        })

        $('.ie-bubble-blocker-frame').css({
            left: left + offset.left + 'px',
            top: top + offset.top + 'px',
            width: $('#map-bubble').width(),
            height: $('#map-bubble').height()
        })

        if (pan) {
            storeLocator.list.pan(bubble.page);
        }

        storeLocator.list.hightLight(rec);
    }
}

/**
 * Store Results List Factory
 */
storeLocator.list = {

    /**
     * Gets the instance of list type
     * @param {string} t type of store to be shown in list
     * @param {object} p store object
     * @param {string[]} s Array of which the store template will be pushed into
     */
    getInstanceOf: function(t,p,s){
        if(storeLocator.list[t])
            return storeLocator.list[t](p,s)
        else
            return storeLocator.list.base(p,s)
    },

    /**
     * Base store type setup
     */
    base: function(p,s){
        // Push each store on result slider
        s.push(storeLocator.templates.resultStore.uTemplate(storeLocator.compileData(u.extend(p, {showDistance:storeLocator.vars.showDistance}))));

        //the stores location icon
        var icon = storeLocator.map.marker.getInstanceOf(p.store.storeType.typeId, p.store.storeType.markerImagePath)

        //create and add store to map
        var point = new GLatLng(p.store.latitude,p.store.longitude)
        var marker = new GMarker(point,icon)
        storeLocator.vars.map.addOverlay(marker);

        storeLocator.vars.stores.push(marker);

        // Add bubble to marker
        GEvent.addListener(marker, "click", function() {
            storeLocator.map.bubble.show(p.index,p,'#store-list-'+p.index,marker,true)
        });
    },

    /**
     * Handles pagination of the store results <br/>
     * Fires Event: storeLocator.fire('paginate-stores', {page:storeLocator.vars.currentPage})
     * @param {int} page Page number to pan to
     */
    pan: function(page) {
        var top = page*(storeLocator.vars.recHeight*-storeLocator.config.resultsPerPage);
        var elm = $('#rec-slider');
        var current = parseInt(elm.css('top'))
        var update = false;

        if (top>current) {
            if (page>=0) {
                elm.animate({"top": top}, "slow")
                update = true;
            } else {
                $('#prev-stores').removeClass('active');
            }
        } else if (top<current) {
            if (page<storeLocator.vars.totalPages) {
                elm.animate({"top": top}, "slow")
                update = true;
            }
        }

        if (update) {
            storeLocator.vars.currentPage = page + 1;
            $('#result-footer .current-page').html(storeLocator.vars.currentPage);
            $('#next-stores').addClass('active');
            $('#prev-stores').addClass('active');
            if (storeLocator.vars.currentPage==storeLocator.vars.totalPages) {
                $('#next-stores').removeClass('active');
            } else if (storeLocator.vars.currentPage==1) {
                $('#prev-stores').removeClass('active');
            }
            storeLocator.fire('paginate-stores', {page:storeLocator.vars.currentPage})
        }
    },

    /**
     * Add css class of 'active' to currently selected store in the list
     * @param {object} obj store object
     */
    hightLight: function(obj) {
        storeLocator.list.goDim();
        $(obj).addClass('active');
    },

    /**
     * Removes css class of 'active' from store in list
     */
    goDim: function() {
        $('#result-list .store').removeClass('active')
    },

    /**
     * Parses the result header template
     * @param {object} Service result jSON
     * @see resultHeader
     */
    updateStoreNumber: function(obj) {
        $('#result-header').html(storeLocator.templates.resultHeader.uTemplate(storeLocator.compileData({count: obj.COUNT})));
    },

    /**
     *  Determines the record height of each store record and stores it for use when paginating
     *  @see storeLocator.list.pan
     */
    setRecHeight: function(){
        var rec = $('#rec-slider .store')
        storeLocator.vars.recHeight = rec.outerHeight()
    }
}

/**
 * Adding u.eventMixin to the storeLocator namespace for firing events
 */
u.extend(storeLocator, u.eventMixin);

/**
 *  Runs GUnload to clear out Google's code
 */
window.onunload = function(){
	GUnload();
}

Array.prototype.df_detect = function(iterator){
    var res;
    var count = this.length, lastIndex = count - 1;
    l:
    while (count--) {
        var r = iterator(this[lastIndex - count]);
        if(r){
            res = this[lastIndex - count];
            break l;
        }
    }
    return res;
}

Number.prototype.df_round = function(places){
	return Math.round(this*(Math.pow(10,places)))/Math.pow(10,places)
}
String.prototype.u_toQueryParams = function(separator, s) {
    s = this.u_s(s);
    var paramsList = s.substring(s.indexOf('?')+1).split('#')[0].split(separator || '&'), params = {}, i, key, value, pair;
    for (i=0; i<paramsList.length; i++) {
        pair = paramsList[i].split('=');
        key = decodeURIComponent(pair[0]);
        value = (pair[1])?decodeURIComponent(pair[1]):undefined;
        if (params[key]) {
            if (typeof params[key] == "string") { params[key] = [params[key]]; }
            params[key].push(value);
        } else { params[key] = value; }
    }
    return params;
}
String.prototype.u_s = function(s) {
    if (s === '' || s) { return s; }
    if (this.str === '' || this.str) { return this.str; }
    return this;
}
/**
 * Registers Global Ajax handlers for turning the loader on and off
 */
$(document).ajaxSend(function(){
    Loader.on()
}).ajaxError(function(e){
    //storeLocator.msg(e)
    Loader.off()
}).ajaxComplete(function(d){
    if($.active <= 0){
        Loader.off()
    }
})

/**
 * Ajax Loader functions
 * @namespace
 */
var Loader = {
    /**
     * Show loader
     */
    on: function(){
        $('#loader').show()
    },

    /**
     * Hide loader
     */
    off: function(){
        $('#loader').hide()
    },

    /**
     * Have Loader follow the mouse cursor
     */
    follow: function(e){
        var loader = $('#loader')
        loader.css({
                'left': e.clientX + 'px',
                'top': e.clientY  + 13 + 'px'
        })
    }
};

/**
 * Radius drawing class
 * @class
 */
var Radius = u.klass({
    /**
     * @constructor
     * @returns {object} drawRadius()
     */
    init: function(pars){
        this.pars = pars
        return this.drawRadius();
    },

    /**
     * @returns {object} GPolygon object. This is the radius overlay
     */
    drawRadius: function() {
        var earth = this.getEarthRadius(this.pars.unitMeasure.toUpperCase());
        var radius = parseFloat(this.pars.radius);
        if (!radius) return undefined;
        var points = this.getPoints(this.pars.lat, this.pars.lng, radius, earth);

        return new GPolygon(points, '#000108', 1, 0.62, '#000108', 0.07);
    },

    /**
     * @param {string} key Accepts 'MILES' or 'KILOMETERS'
     * @returns {int} Earth radius in mile or kilometers
     */
    getEarthRadius: function(key) {
        return {
            'MILES': 3963.1676,
            'KILOMETERS': 6378.1
        }[key]
    },

    /**
     * Insane math to find the points of which will make up the radius overlay
     * @param {int} lat current map center latitude
     * @param {int} lng current map center longitude
     * @param {int} radius radius of overlay
     * @param {int} earth radius of the earth
     * @returns {object[]} Array of GLatLng objects for drawing the radius overlay
     */
    getPoints: function(lat, lng, radius, earth) {
        lat = (lat * Math.PI) / 180;
        lon = (lng * Math.PI) / 180;
        var d = parseFloat(radius) / earth;
        var points = [];
        for (x = 0; x <= 360; x++) {
            brng = x * Math.PI / 180; //rad
            var destLat = Math.asin(Math.sin(lat) * Math.cos(d) + Math.cos(lat) * Math.sin(d) * Math.cos(brng));
            var destLng = ((lon + Math.atan2(Math.sin(brng) * Math.sin(d) * Math.cos(lat), Math.cos(d) - Math.sin(lat) * Math.sin(destLat))) * 180) / Math.PI;
            destLat = (destLat * 180) / Math.PI;
            points.push(new GLatLng(destLat, destLng));
        }
        return points;
    }

})

var Cookies = u.klass({
    init: function(){
	return this
    },

    setCookie:function(name, data, path, expires){
	var today = new Date();
	var expire = new Date();
	if(expires)
	    expires = parseInt(expires)*24*60*60*1000;

	expire.setTime(today.getTime() + expires)

	document.cookie = name+"="+ data + ";path="+ path +";expires="+expire.toGMTString();
    },

    getCookie:function(name) {
	var start = document.cookie.indexOf( name + "=" );
	var len = start + name.length + 1;
	if ( ( !start ) && ( name != document.cookie.substring( 0, name.length ) ) ) {
	    return null;
	}
	if ( start == -1 ) return null;
	var end = document.cookie.indexOf( ";", len );

	if ( end == -1 ) end = document.cookie.length;
	return unescape( document.cookie.substring( len, end ) );
    },

    deleteCookie:function(name){
	if ( this.getCookie(name) )
	    document.cookie = name + "=" + ( ( path ) ? ";path=" + path : "") + ( ( domain ) ? ";domain=" + domain : "" ) + ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
    }
})

/*
 * Extending JSU utils
*/

u.dataStore = {}

u.templateUtils.getData = function(n){
    return u.dataStore[n.getAttribute('data-jsuid')];
}

u.templateUtils.setData = function (d) {
    var uid = 'jsu'.uId();
    u.dataStore[uid] = d;
    return ' data-jsuid="{{}}" '.uInject(uid);
}

//"dasdasd {{ utils.setData(this.a) }} asdasdasd".uTemplate({a:1.345345})

/*
 * End JSU extend
 *
*/
})( jQuery )
